package solver

import (
	"fmt"
	"log"
	"slices"
	"strings"
)

// Finds the start and end positions of the x-cycle in the Sudoku grid
func (g *Grid) XCycle1() (used bool) {
	all := g.findCandAllCells()
	for cand, bases := range all {
		for _, start := range bases {
			result := g.findCycleFromStrong1(cand, []*Cell{start}, bases)
			if len(result) == 0 {
				continue
			}
			//fmt.Println(cand, len(result), result)
			for i := 1; i < len(result); i += 2 {
				var cells []*Cell
				if i+1 == len(result) {
					cells = []*Cell{result[i], result[0]}
				} else {
					cells = result[i : i+2]
				}
				if g.clearupSeenCand(cand, cells) {
					//fmt.Println("clearup", cells)
					used = true
				}
			}
			//fmt.Println(used, cand, result)
			if used {
				logXCycle1(cand, result)
				return
			}
		}
	}
	return
}

func logXCycle1(cand int, cells []*Cell) {
	var b strings.Builder

	link := "="
	for _, cell := range cells {
		fmt.Fprintf(&b, "r%vc%v%v", cell.rowNum, cell.colNum, link)
		if link == "=" {
			link = "-"
		} else {
			link = "="
		}
	}
	fmt.Fprintf(&b, " %v X-Cycle Type1", toValue(cand))
	log.Print(b.String())
}
func (g *Grid) findCandAllCells() [][]*Cell {
	all := [9][]*Cell{}
	for _, row := range g.rows {
		for _, cell := range row.cells {
			if cell.solved() {
				continue
			}
			for _, cand := range cell.cands.GetList() {
				all[cand] = append(all[cand], cell)
			}
		}
	}
	return all[:]
}

func (g *Grid) findCycleFromStrong1(cand int, links, bases []*Cell) []*Cell {
	last := links[len(links)-1]
	strongs := []*Cell{}
	for _, cell := range bases {
		if slices.Contains(links, cell) {
			continue
		}
		houses := g.getSameHouses(last, cell)
		if len(houses) == 0 {
			continue
		}
		if !isOnlyTwoInHouses(cand, []*Cell{last, cell}, houses) {
			continue
		}
		strongs = append(strongs, cell)
	}

	for _, strong := range strongs {
		result := g.findCycleFromWeak1(cand, append(links, strong), bases)
		if len(result) != 0 {
			return result
		}
	}
	return []*Cell{}
}

func (g *Grid) findCycleFromWeak1(cand int, links, bases []*Cell) []*Cell {
	head := links[0]
	last := links[len(links)-1]
	if sameHouse([]*Cell{head, last}) && len(links) > 4 {
		return links
	}

	weaks := []*Cell{}
	for _, cell := range bases {
		if slices.Contains(links, cell) {
			continue
		}
		if !sameHouse([]*Cell{last, cell}) {
			continue
		}
		weaks = append(weaks, cell)
	}
	for _, cell := range weaks {
		result := g.findCycleFromStrong1(cand, append(links, cell), bases)
		if len(result) != 0 {
			return result
		}
	}
	return []*Cell{}
}

func (g *Grid) getSameHouses(x, y *Cell) (houses []House) {
	if x.rowNum == y.rowNum {
		houses = append(houses, g.rows[x.rowNum])
	}
	if x.colNum == y.colNum {
		houses = append(houses, g.cols[x.colNum])
	}
	if x.boxNum == y.boxNum {
		houses = append(houses, g.boxes[x.boxNum])
	}
	return
}

func isOnlyTwoInHouses(cand int, cells []*Cell, houses []House) bool {
	for _, house := range houses {
		ok := true
		for _, cell := range house.cells {
			if cell == cells[0] || cell == cells[1] {
				continue
			}
			if cell.cands.Has(cand) {
				ok = false
			}
		}
		if ok {
			return true
		}
	}
	return false
}

func (g *Grid) XCycle2() (used bool) {
	all := g.findCandAllCells()
	for cand, bases := range all {
		for _, start := range bases {
			result := g.findCycleFromStrong2(cand, []*Cell{start}, bases)
			if len(result) == 0 {
				continue
			}
			g.solveOneCell(result[0].rowNum, result[0].colNum, toValue(cand))
			logXCycle2(cand, result)
			used = true
			return
		}
	}
	return
}

func (g *Grid) findCycleFromStrong2(cand int, links, bases []*Cell) []*Cell {
	head := links[0]
	last := links[len(links)-1]
	if len(links) >= 5 {
		houses := g.getSameHouses(head, last)
		if len(houses) != 0 && isOnlyTwoInHouses(cand, []*Cell{head, last}, houses) {
			return links
		}
	}

	strongs := []*Cell{}
	for _, cell := range bases {
		if slices.Contains(links, cell) {
			continue
		}

		houses := g.getSameHouses(last, cell)
		if len(houses) == 0 {
			continue
		}
		if !isOnlyTwoInHouses(cand, []*Cell{last, cell}, houses) {
			continue
		}
		strongs = append(strongs, cell)
	}

	for _, strong := range strongs {
		result := g.findCycleFromWeak2(cand, append(links, strong), bases)
		if len(result) != 0 {
			return result
		}
	}
	return []*Cell{}
}

func (g *Grid) findCycleFromWeak2(cand int, links, bases []*Cell) []*Cell {
	last := links[len(links)-1]

	weaks := []*Cell{}
	for _, cell := range bases {
		if slices.Contains(links, cell) {
			continue
		}
		if !sameHouse([]*Cell{last, cell}) {
			continue
		}
		weaks = append(weaks, cell)
	}
	for _, cell := range weaks {
		result := g.findCycleFromStrong2(cand, append(links, cell), bases)
		if len(result) != 0 {
			return result
		}
	}
	return []*Cell{}
}

func logXCycle2(cand int, cells []*Cell) {
	var b strings.Builder

	n := len(cells) - 1
	link := "="
	for _, cell := range cells[:n] {
		fmt.Fprintf(&b, "r%vc%v%v", cell.rowNum, cell.colNum, link)
		if link == "=" {
			link = "-"
		} else {
			link = "="
		}
	}
	fmt.Fprintf(&b, "r%vc%v=", cells[n].rowNum, cells[n].colNum)
	fmt.Fprintf(&b, " %v X-Cycle Type2", toValue(cand))
	log.Print(b.String())
}

func (g *Grid) XCycle3() (used bool) {
	all := g.findCandAllCells()
	for cand, bases := range all {
		for _, start := range bases {
			result := g.findCycleFromStrong3(cand, []*Cell{start}, bases)
			if len(result) == 0 {
				continue
			}
			result[len(result)-1].removeCand(cand)
			logXCycle3(cand, result)
			used = true
			return
		}
	}
	return
}

func (g *Grid) findCycleFromStrong3(cand int, links, bases []*Cell) []*Cell {
	head := links[0]
	last := links[len(links)-1]
	if len(links) >= 5 && sameHouse([]*Cell{head, last}) {
		return links
	}

	strongs := []*Cell{}
	for _, cell := range bases {
		if slices.Contains(links, cell) {
			continue
		}

		houses := g.getSameHouses(last, cell)
		if len(houses) == 0 {
			continue
		}
		if !isOnlyTwoInHouses(cand, []*Cell{last, cell}, houses) {
			continue
		}
		strongs = append(strongs, cell)
	}

	for _, strong := range strongs {
		result := g.findCycleFromWeak3(cand, append(links, strong), bases)
		if len(result) != 0 {
			return result
		}
	}
	return []*Cell{}
}

func (g *Grid) findCycleFromWeak3(cand int, links, bases []*Cell) []*Cell {
	last := links[len(links)-1]

	weaks := []*Cell{}
	for _, cell := range bases {
		if slices.Contains(links, cell) {
			continue
		}
		if !sameHouse([]*Cell{last, cell}) {
			continue
		}
		weaks = append(weaks, cell)
	}
	for _, cell := range weaks {
		result := g.findCycleFromStrong3(cand, append(links, cell), bases)
		if len(result) != 0 {
			return result
		}
	}
	return []*Cell{}
}

func logXCycle3(cand int, cells []*Cell) {
	var b strings.Builder

	n := len(cells) - 1
	link := "="
	for _, cell := range cells[:n] {
		fmt.Fprintf(&b, "r%vc%v%v", cell.rowNum, cell.colNum, link)
		if link == "=" {
			link = "-"
		} else {
			link = "="
		}
	}
	fmt.Fprintf(&b, "r%vc%v-", cells[n].rowNum, cells[n].colNum)
	fmt.Fprintf(&b, " %v X-Cycle Type3", toValue(cand))
	log.Print(b.String())
}
